

import javax.swing.border.*; //导入用到的包
import javax.swing.*;
import java.util.Vector;
import java.awt.*;
import java.awt.event.*;


//主图形窗口类
public class nQueenV4 extends JFrame {

    private static final long serialVersionUID = 1L;//swing默认的不用理会
    private JPanel contentPane;
    private static JPanel resultPanel = new JPanel();//结果面板
    private JScrollPane scrollPane;
    private JPanel upPanel;
    private JTextField inputField;
    private JButton confirmButton, clearBtn, aboutBtn, helpBtn;
    private JTextField outputField;
    private JLabel label2, clickHere;
    private JCheckBox isShowBox;
    private JRadioButton recursion, flashBack, myWay;
    private ButtonGroup btnGroup;
    int num;
    nQueenExplore nQ;

    public static void main(String[] args) { //主函数入口
        EventQueue.invokeLater(new Runnable() { //自动生成的，不用理会
            public void run() {
                try {
                    nQueenV4 mainframe = new nQueenV4();
                    mainframe.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    //监听控件的动作
    private ActionListener actionListener = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            Object source = e.getSource();
            if (source.equals(helpBtn)) //点击到帮助按钮的时候
                help();
            else if (source.equals(aboutBtn))//点击到关于作者按钮的时候
                aboutAuthor();
            else if (source.equals(confirmButton) || source.equals(inputField))
                start(); //点击到确认按钮或者回车的时候
            else if (source.equals(clearBtn)) { //清空按钮的时候
                resultPanel.removeAll();
                resultPanel.updateUI();
            }
        }
    };

    //构造函数
    public nQueenV4() throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());  //设置系统的外观
        setResizable(false); //不可更改大小，不可以最大化

        new MyDialog();  //弹出欢迎界面

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置默认关闭窗口的方法
        setTitle("N皇后问题求解，输入的n最好不要超过12，13时已经运行1分钟以上了"); //设置标题
        setBounds(100, 10, 1400, 900);
        contentPane = new JPanel();//内容面板
        contentPane.setLayout(new BorderLayout());
        contentPane.setBorder(new LineBorder(Color.CYAN, 3, true));
        setContentPane(contentPane);

        resultPanel.setBackground(new Color(150, 100, 60));

        upPanel = new JPanel(); //主界面上方的控制部分的容器
        upPanel.setBorder(new LineBorder(Color.cyan, 3));
        upPanel.setBackground(new Color(160, 160, 160));

        clickHere = new JLabel();
        clickHere.setIcon(new ImageIcon(getClass().getResource("zzh3.gif")));
        clickHere.setPreferredSize(new Dimension(200, 100));
        clickHere.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                new EightQueenDemoFrame(getN());//弹出逐步显示过程的界面
            }
        });
        upPanel.add(clickHere);

        scrollPane = new JScrollPane();//滚动面板，能够滚动显示所有结果
        scrollPane.getHorizontalScrollBar().setUnitIncrement(50);  //把滚动条的单位刻度搞大，方便鼠标滚动

        isShowBox = new JCheckBox("是否绘制结果", true);
        upPanel.add(isShowBox);

        btnGroup = new ButtonGroup();

        recursion = new JRadioButton("递归法");
        upPanel.add(recursion);
        flashBack = new JRadioButton("回溯法", true);
        upPanel.add(flashBack);
        myWay = new JRadioButton("我的方法");
        upPanel.add(myWay);

        btnGroup.add(recursion);
        btnGroup.add(flashBack);
        btnGroup.add(myWay);

        JLabel labelTip = new JLabel("请输入：");
        upPanel.add(labelTip);

        inputField = new JTextField("8", 4);
        inputField.addActionListener(actionListener);
        upPanel.add(inputField);

        confirmButton = new JButton("确认");
        confirmButton.addActionListener(actionListener);
        upPanel.add(confirmButton);

        clearBtn = new JButton("清屏");
        clearBtn.addActionListener(actionListener);
        upPanel.add(clearBtn);

        label2 = new JLabel("一共有结果：");
        upPanel.add(label2);

        outputField = new JTextField(30);
        outputField.setFont(new Font("黑体", Font.BOLD, 16));
        outputField.setEditable(false);
        upPanel.add(outputField);

        aboutBtn = new JButton("关于作者");
        aboutBtn.addActionListener(actionListener);
        upPanel.add(aboutBtn);

        helpBtn = new JButton("帮助");
        helpBtn.addActionListener(actionListener);
        upPanel.add(helpBtn);

        scrollPane.setViewportView(resultPanel);

        contentPane.add(upPanel, BorderLayout.NORTH);
        contentPane.add(scrollPane, BorderLayout.CENTER);
        //设置对话框的字体
        UIManager.put("OptionPane.messageFont", new Font("微软雅黑", 1, 24));
        UIManager.put("OptionPane.buttonFont", new Font("微软雅黑", 1, 24));
    }
    //获取输入框的数值
    private int getN() {
        String in = inputField.getText();
        int n;
        try {
            n = Integer.valueOf(in);
            if (n < 1 || (n > 12 && isShowBox.isSelected()) || (n > 14 && !isShowBox.isSelected())) {
                JOptionPane.showMessageDialog(this, "太大或者太小,已经更改为默认",
                        "数值不符合范围", JOptionPane.CLOSED_OPTION);
                inputField.setText("8");
                return 8;
            }
        } catch (NumberFormatException e) {
            n = 8;  //如果输入了其他奇怪的东西无法转换成数字，则令n=8
        }
        return n;
    }
    //输出结果后更新界面，跳出提示
    private void updateResult() {
        outputField.setText(num + "皇后解法:" + nQ.count + "种");
        outputField.updateUI();
        resultPanel.updateUI();
        String message = num + "皇后解法:" + nQ.count + "种;"
                + "探索次数为：" + nQ.exploreCount;
        JOptionPane.showMessageDialog(this, message, "结果", JOptionPane.CLOSED_OPTION);
    }
    //开始计算
    private void start() {
        resultPanel.removeAll();
        resultPanel.updateUI();
        num = getN();
        //以参数n，结果面板，是否分解，是否绘制棋盘，构造一个n皇后探索对象
        nQ = new nQueenExplore(num, resultPanel, isShowBox.isSelected());

        if (recursion.isSelected()) {
            nQ.recursion_find(1); //递归法
            updateResult();
        } else if (myWay.isSelected()) {
            nQ.stack_find(); //非递归的栈解法
            updateResult();

        } else {
            nQ.flashBack_find(); //回溯法
            updateResult();
        }
    }

    private void help() {
        String message = "在输入框中输入一个数字n(不输入默认是8)，会在下面绘出所有摆放方法，\n最好大于4，小于13，再大的话要算很久\n"
                + "用鼠标滚轮或者鼠标拖到滑块左右滑动查看\n" + "有三种可选方法，递归法，回溯法，我的方法\n"
                + "点击【点一下玩一年】可以手动或者自动慢动作的观看探索的详细过程";
        JOptionPane.showMessageDialog(this, message, "帮助", JOptionPane.CANCEL_OPTION);
    }

    private void aboutAuthor() {
        String message = "作者：霍华骑\n 班级：计算机172\n 邮箱：2696626767@qq.com";
        JOptionPane.showMessageDialog(this, message, "关于作者", JOptionPane.CANCEL_OPTION);
    }

}

//自定义面板，用来绘制一个棋盘
class MyPanel extends JPanel {

    private static final long serialVersionUID = 1L;
    private final int width = 25;
    private int n, count;
    private int[] chessboard = new int[20];
    private ImageIcon icon = new ImageIcon(getClass().getResource("Queen.png"));

    public MyPanel(int n, int count, int[] q) {
        this.n = n;
        this.count = count;
        System.arraycopy(q, 1, chessboard, 1, n);
        this.setLayout(null);
        this.setPreferredSize(new Dimension(2 * n * width + 3 * width, 2 * n * width + 3 * width));//棋盘panel的总大小
        this.setBorder(BorderFactory.createLineBorder(Color.darkGray, 3));//设置边界
        this.setBackground(Color.orange);
    }

    @Override //重新paint方法达到绘制棋盘的目的
    public void paint(Graphics g) {
        super.paint(g);
        g.setColor(Color.black);
        g.setFont(new Font("", Font.BOLD, 20));
        g.drawString("第 " + count + " 种摆放方法", width, width);

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if ((i % 2 != 0 && j % 2 != 0) || (i % 2 == 0 && j % 2 == 0))  //设置国际象棋黑白相间的颜色
                    g.setColor(new Color(25, 200, 200));
                else
                    g.setColor(new Color(50, 50, 50));
                g.fillRect(2 * (j) * width, 2 * (i) * width, 2 * width, 2 * width);

                if (chessboard[i] == j) { //如果有棋子，画出来
                    g.drawImage(icon.getImage(), 2 * (j) * width, 2 * (i) * width,
                            2 * width, 2 * width, null);
                    g.setColor(Color.black);
                    g.drawString(j + "", width, 2 * i * width + width);
                }
            }
        }
    }

}

//欢迎界面
class MyDialog extends JDialog {

    JTextArea userLabel;
    JLabel  bgLabel;
    JButton loginBtn;

    public MyDialog() {
        setResizable(false);
        setTitle("欢迎");
        setBounds(500, 300, 500, 309);
        setModal(true);
        Container cp = getContentPane();
        cp.setLayout(null);

        bgLabel = new JLabel();
        bgLabel.setIcon(new ImageIcon(getClass().getResource("chess.jpg")));
        bgLabel.setBounds(0, -19, 500, 309);

        userLabel = new JTextArea("欢迎来到N皇后问题演示\n");
        userLabel.setForeground(Color.BLACK);
        userLabel.setEditable(false);
        userLabel.setFont(new Font("微软雅黑", 1, 20));
        userLabel.setBackground(new Color(150,150,150,150));
        userLabel.setBounds(100, 50, 300, 150);


        loginBtn = new JButton("确认");
        loginBtn.setBounds(200, 220, 100, 50);
        loginBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dispose();//释放关闭窗体
            }
        });

        cp.add(loginBtn);
        cp.add(userLabel);
        cp.add(bgLabel);
        setVisible(true);
    }

}

class EightQueenDemoFrame extends JFrame {

    int num, count = 0, h = 1, v = 1, speed = 200;
    long times = 0;
    static int[] chessboard;
    MyStack<Integer> stack = new MyStack<Integer>();
    Container cp = getContentPane();
    DemoLeftBoard leftPane;
    DemoMainBoard mainPane;
    JButton nextBtn, resetBtn;
    JTextArea tip;
    JLabel resultLabel;
    JLabel countLabel;
    JCheckBox auto;
    JComboBox<String> speedMode;
    boolean isAuto, stop;
    ActionListener listener;

    public EightQueenDemoFrame(int n) {
        num = n;
        chessboard = new int[num + 2];
        listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Object o = e.getSource();
                if (o.equals(nextBtn)) {
                    if (!stop)
                        next();
                } else if (o.equals(resetBtn)) {  //重置
                    for (int i = 0; i <= num + 1; i++) {
                        chessboard[i] = 0;
                        leftPane.labelSet[i].setText("null");
                    }
                    stop = false;
                    chessboard[1] = 1;
                    h = v = mainPane.x = mainPane.y = 1;
                    times = count = 0;
                    leftPane.top = -1;
                    stack.clear();
                    repaint();
                } else if (o.equals(auto)) {
                    Thread autoThread = new Thread() {
                        @Override
                        public void run() {
                            while (isAuto && !stop) {
                                next();
                                mySleep(speed);
                            }
                        }
                    };
                    autoThread.start();
                    if (auto.isSelected()) {
                        isAuto = true;
                    } else {
                        isAuto = false;
                    }
                } else if (o.equals(speedMode)) {
                    switch (speedMode.getSelectedIndex()) {
                        case 0:
                            speed = 200;
                            break;
                        case 1:
                            speed = 90;
                            break;
                        case 2:
                            speed = 400;
                            break;
                    }
                }
            }
        };

        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setTitle(num + "皇后逐步演示");
        setBounds(50, 10, 1400, 900);
        setResizable(false);
        cp.setBackground(Color.gray);

        leftPane = new DemoLeftBoard(num);
        mainPane = new DemoMainBoard(num);

        nextBtn = new JButton("手动下一步");
        nextBtn.setBounds(1200, 600, 160, 60);
        nextBtn.addActionListener(listener);
        nextBtn.setFont(new Font("", 1, 18));

        resetBtn = new JButton("重置");
        resetBtn.setBounds(1100, 800, 80, 60);
        resetBtn.addActionListener(listener);
        resetBtn.setFont(new Font("", 1, 18));

        auto = new JCheckBox("自动");
        auto.setBounds(1200, 800, 80, 60);
        auto.addActionListener(listener);
        auto.setFont(new Font("", 1, 18));

        String[] list = {"中", "快", "慢"};
        speedMode = new JComboBox<>(list);
        speedMode.setFont(new Font("", 1, 25));
        speedMode.setBounds(1300, 800, 80, 60);
        speedMode.setEditable(false);
        speedMode.addActionListener(listener);

        tip = new JTextArea("");
        tip.setBounds(1200, 50, 200, 200);
        tip.setEditable(false);
        tip.setFont(new Font("", 1, 25));
        tip.setAlignmentX(JTextArea.CENTER_ALIGNMENT);
        tip.setLineWrap(true); //换行
        tip.setWrapStyleWord(true);//换行不坏字
        tip.setBackground(new Color(100, 100, 100, 50));

        resultLabel = new JLabel("摆放方法有:" + count + "种");
        resultLabel.setBounds(1200, 270, 200, 50);
        resultLabel.setFont(new Font("", 1, 20));

        countLabel = new JLabel("探索次数:" + times + "次");
        countLabel.setBounds(1200, 340, 200, 50);
        countLabel.setFont(new Font("", 1, 20));

        cp.add(resultLabel);
        cp.add(countLabel);
        cp.add(auto);
        cp.add(speedMode);
        cp.add(tip);
        cp.add(nextBtn);
        cp.add(resetBtn);
        cp.setLayout(null);
        cp.add(leftPane);
        cp.add(mainPane);
        setVisible(true);


    }

    public void paint(String s, int h, int v) {
        Thread aa = new Thread("aa") {
            @Override
            public void run() {
                tip.setText(s); //绘制提示窗口
                mainPane.put(chessboard, h, v); //绘制棋盘
                repaint();
            }
        };
        aa.start();  //绘制
    }

    public void next() {  //每调用一次，执行一步，和法一的思想一样，只是分步运行

        Thread NT = new Thread() { //为了不影响主线程，新建一个线程
            @Override
            public void run() {
                synchronized (chessboard) { //锁定棋盘，在运行期间不许其他线程修改
                    if (h <= num) {
                        times++; //探索次数加一
                        chessboard[h] = v;
                        paint("尝试(" + h + "," + v + ")", h, v); //摆放h，v
                        mySleep(speed); //暂停一会儿
                        if (v <= num) {
                            if (check(h, v)) {
                                stack.push(v);
                                leftPane.push(h, v);
                                h++;
                                v = 1;
                                paint("可以放，入栈，进入下一行", h, v);
                            } else { //不能放
                                v++;
                                chessboard[h] = 0;
                                paint("不能放，找下一个列", h, v);
                            }
                        } else { //v>num
                            if (stack.isEmpty()) {  //在这里退出
                                stop = true;
                                return;
                            }
                            paint("不能放，而且这一行已经找完，回溯", h, v);
                            leftPane.pop();
                            h--;
                            v = stack.pop() + 1;
                        }
                    } else {  //h>num
                        h--;
                        v = stack.pop() + 1;
                        leftPane.pop();
                        count++;
                        paint("找到一个摆放方法，回溯继续", h, v);

                    }
                }
                Thread aa = new Thread() {
                    @Override
                    public void run() {
                        resultLabel.setText("摆放方法有:" + count + "种");
                        countLabel.setText("探索次数:" + times + "次");
                        repaint();
                    }
                };
                aa.start();  //绘制

            }
        };
        NT.start();

    }

    public boolean check(int i, int j) {
        boolean flag = true;
        if (i < 1 || i > num || j < 1 || j > num) //超出棋盘
            return false;
        for (int k = i - 1; k >= 1; k--) {  //从第n-1行开始检查到第一行，看看有没有在同一条线上
            if (j == chessboard[k]) {
                flag = false; //上方有棋子，失败
                break;
            }
            if (Math.abs(i - k) == Math.abs(j - chessboard[k])) {
                //左斜上方或者右斜上方有棋子
                flag = false;
                break;
            }
        }
        return flag;
    }

    public void mySleep(int t) { //睡眠t 毫秒
        try {
            Thread.sleep(t);
        } catch (Exception e) {
        }
    }

}
//模拟栈的面板
class DemoLeftBoard extends JPanel {

    JLabel[] labelSet = new JLabel[20];
    int top = -1, num;

    public DemoLeftBoard(int n) {
        num = n;
        setLayout(null);
        setBounds(10, 10, 60, 850);
        setBackground(Color.lightGray);
        for (int i = 0; i <= num + 1; i++) {
            labelSet[i] = new JLabel("null");
            labelSet[i].setBounds(2, 20 + i * 70, 50, 50);
            labelSet[i].setBorder(new LineBorder(Color.black, 3));
            labelSet[i].setFont(new Font("", 1, 24));
            labelSet[i].setHorizontalAlignment(JLabel.CENTER);
            this.add(labelSet[i]);
        }
        setVisible(true);
    }

    public void push(int i, int j) {
        top++;
        labelSet[top].setText(j + "");

    }

    public void pop() {
        labelSet[top].setText("null");
        top--;
    }

}
//模拟棋盘的面板
class DemoMainBoard extends JPanel {
    ImageIcon icon = new ImageIcon(getClass().getResource("Queen.png"));
    int x = 1, y = 1, num; //当前行，当前列，n皇后的个数
    int[] chessboard = new int[20];

    public DemoMainBoard(int n) {
        num = n;
        setBounds(100, 10, 1100, 850);
        setBackground(Color.lightGray);
        setVisible(true);
    }

    @Override
    public void paint(Graphics g) {
        for (int i = 1; i <= num; i++) {
            for (int j = 1; j <= num; j++) {
                //设置国际象棋黑白相间的颜色
                if ((i % 2 != 0 && j % 2 != 0) || (i % 2 == 0 && j % 2 == 0))
                    g.setColor(new Color(25, 200, 200));
                else
                    g.setColor(new Color(50, 50, 50));
                g.fillRect(j * 70 - 60, i * 70 - 60, 70, 70);
                if (chessboard[i] == j) {
                    g.drawImage(icon.getImage(), j * 70 - 60, i * 70 - 60, 70, 70, null);
                }
            }
        }
        //绘制两条直线，表示当前是第x行，y列
        g.setColor(Color.red);
        g.drawLine(y * 70 - 25, 0, y * 70 - 25, num * 70 + num);
        g.drawLine(0, x * 70 - 25, num * 70 + num, x * 70 - 25);
    }

    public void put(int[] c, int h, int v) { //更改棋盘的参数
        x = h;
        y = v;
        chessboard = c;
    }
}
